package com.zero.demo.jms.util;

import com.zero.demo.jms.dao.DynamicCompDao;
import com.zero.demo.jms.entity.DynamicCompDto;
import com.zero.demo.jms.util.cache.AbstractEhCache;
import groovy.lang.GroovyClassLoader;
import lombok.extern.log4j.Log4j2;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.stereotype.Component;

import javax.tools.JavaCompiler;
import javax.tools.JavaFileObject;
import javax.tools.StandardJavaFileManager;
import javax.tools.ToolProvider;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.locks.Lock;

/**
 * @author zengzr
 * @desc
 * @Date 2019/5/5 17:43
 */
@Component
@Log4j2
public class DynmiaClassCacheUtils  extends AbstractEhCache<Class<?>> {

    private static String compliePath;
    private static String classPathPath = System.getProperty("java.class.path");
    private final static String dynamicBeanFix = "javaDynamic_";

    private static String configClassPath = "";

    private static String iomClassesPath = System.getProperty("java.class.path");
    private String osName;

    private static String javaFilePath;

    @Autowired
    BusiDynamicCompCacheUtils busiDynamicCompCacheUtils;

    @Autowired
    DynamicCompDao dynamicCompDao;


    @Autowired
    DefaultListableBeanFactory defaultList;

    @Override
    public void initCache() {
        try {
            initClassPath();
            this.removeDynamicBean();
            createClassOriginally();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }



    public static String getJavaHome() {
        String javaHome = System.getProperty("java.home");
        return javaHome;
    }

    public void initClassPath() throws Exception {
        osName = System.getProperty("os.name");
        String myClassPathSeparator;
        ClassLoader classLoader = DynmiaClassCacheUtils.class
                .getClassLoader();
        String classLoaderSimpleName = classLoader.getClass().getSimpleName();
        log.info("类加载器classLoader=" + classLoader.getClass().getName());
        boolean isWebApp = false;
        boolean isJettyRun = classLoaderSimpleName
                .contains("WebAppClassLoader");
        if (isJettyRun) {
            log.info("jetty加载器启动!!!");
            isWebApp = true;
        } else {
            // 判断是否是tomcat的加载器
            isWebApp = classLoaderSimpleName.contains("WebappClassLoader");
            if (isWebApp) {
                log.info("tomcat加载器启动!!!");
            }
        }

        String classesPath = StringUtils.isEmpty(configClassPath)
                ? iomClassesPath
                : configClassPath;
        if (osName.indexOf("Windows") != -1) {
            myClassPathSeparator = ";";
        } else {
            myClassPathSeparator = ":";
        }
        if (isWebApp) {
            // 如果是web则需要找到lib包路径，并遍历初所有的jar包加到classpath中
            int lastIndex = classesPath.lastIndexOf("classes");
            if (lastIndex > 0) {
                classesPath = classesPath.substring(0, lastIndex + 7);
            }
            // 找到lib包路径
            String libPath = classesPath.substring(0, lastIndex) + "lib";
            // 获取并拼接lib下面的所有jar路径
            String jarsPath = "";
            File libFilePath = new File(libPath);
            log.info("程序计算出来的lib路径为：" + libPath);
            if (libFilePath.exists()) {
                if (!libFilePath.isDirectory()) {
                    throw new Exception("程序计算出的libPath不是文件夹,libPath=" + libPath);
                } else {
                    File[] libFiles = libFilePath.listFiles();
                    for (int i = 0; i < libFiles.length; i++) {
                        File jarFile = libFiles[i];
                        if (jarFile.getName().endsWith("jar")
                                || jarFile.getName().endsWith("JAR")) {
                            if (i > 0) {
                                jarsPath += myClassPathSeparator
                                        + jarFile.getAbsolutePath();
                            } else {
                                jarsPath += jarFile.getAbsolutePath();
                            }
                        }
                    }
                }
            } else {
                throw new Exception("程序计算出的libPath不存在,libPath=" + libPath);
            }
            classesPath += myClassPathSeparator + classesPath + ".*"
                    + myClassPathSeparator;
            classesPath += jarsPath;
            classPathPath += myClassPathSeparator + classesPath
                    + myClassPathSeparator;
        } else {
            // 非web
            classesPath += myClassPathSeparator + classesPath + ".*"
                    + myClassPathSeparator;
            classPathPath += myClassPathSeparator + classesPath;
        }
        // 删除以前的动态组建
        String oldFile = compliePath + File.separator + "javaDynamicScript"
                + File.separator;
        File file = new File(oldFile);
        if (file.isDirectory()) {
            File[] subFiles = file.listFiles();
            if (ArrayUtils.isNotEmpty(subFiles)) {
                for (File subFile : subFiles) {
                    subFile.deleteOnExit();
                }
            }
        }
        // log.info("动态编译的classPath为:" + classPathPath);
        log.info("java版本:" + System.getProperty("java.version"));
    }

    public Class<?> getClassById(Long osBusiDynamicCompId) throws Exception {
        String key = Long.toString(osBusiDynamicCompId);
        Class<?> ret = getByKey(key);
        if (ret == null) {
            ret = compileClass(Long.toString(osBusiDynamicCompId));
        }
        return ret;
    }

    public <T> T getInstanceById(Long osBusiDynamicId, Class<T> clazz) throws Exception {
        String beanId = dynamicBeanFix + osBusiDynamicId;
        T result = ApplicationContextUtils.getBeanAllowNull(beanId, clazz);
        if (result == null) {
            Lock l = LockUtils.getLock(DynmiaClassCacheUtils.class, beanId);
            try {
                l.lock();
                result = ApplicationContextUtils.getBeanAllowNull(beanId,
                        clazz);
                if (result == null) {
                    getClassById(osBusiDynamicId);
                    result = ApplicationContextUtils.getBeanAllowNull(beanId,
                            clazz);
                }
            } finally {
                l.unlock();
            }
        }
        return result;
    }

    private Class<?> compileClass(String osBusiDynamicCompId) throws Exception {
        Class<?> clazz = null;
        DynamicCompDto dynamicCompDto = busiDynamicCompCacheUtils.getById(Long.valueOf(osBusiDynamicCompId));

        Lock l = LockUtils.getLock(DynmiaClassCacheUtils.class, osBusiDynamicCompId);
        log.info("开始获取锁:" + osBusiDynamicCompId + "...");
        l.lock();
        try {
            log.info("成功获取锁:" + osBusiDynamicCompId + "!!!");
            clazz = getClassById(Long.valueOf(osBusiDynamicCompId));
            if (clazz == null) {
                log.info("<JavaDynamicScriptExecutor>: get a lock ...");
                String classBody = busiDynamicCompCacheUtils.getBodyById(dynamicCompDto.getId());
                clazz = createClass(osBusiDynamicCompId, dynamicCompDto.getCompCode(), classBody);
            }
            log.info("成功加载class:" + osBusiDynamicCompId + "!!!");
        } catch (Exception e) {
            throw new Exception("动态组件:osBusiDynamicCompId="
                    + osBusiDynamicCompId + ",编译失败!", e);
        } finally {
            l.unlock();
        }
        return clazz;
    }

   /* *
     * 创建Java文件并将文件编译为class文件
     *
     * @param shellScriptId
     * @param scriptCode
     * @param scriptBody
     * @return
     * @throws Exception*/

    public Class<?> createClass(String shellScriptId, String scriptCode,
                                String scriptBody) throws Exception {
        Class<?> clazz = null;
        Object resultObj = null;
        String className = null;

        try {
            GroovyClassLoader groovyClassLoader = new GroovyClassLoader();
            //里面的文本是Java代码,但是我们可以看到这是一个字符串我们可以直接生成对应的Class<?>对象,而不需要我们写一个.java文件
            clazz = groovyClassLoader.parseClass(scriptBody);
            cacheClazz(className, shellScriptId, clazz);
        } catch (Exception e) {
            log.error("GroovyClassLoader动态编译异常!!", e);
            loadClassByJava(scriptCode, scriptBody);
            clazz = cacheClazz(className, shellScriptId);
        }

        BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(clazz);
        defaultList.setAllowBeanDefinitionOverriding(true);
        defaultList.registerBeanDefinition(dynamicBeanFix + shellScriptId, beanDefinitionBuilder.getBeanDefinition());
        return clazz;
    }


    /**
     * 用 java 的方式加载动态组件。
     *
     * @param scriptBody 组件内容
     * @param scriptCode 组件编码
     * @return
     * @author   on 2017/12/4 18:06
     * @version 1.0.0*/

    public void loadClassByJava(String scriptCode, String scriptBody) throws IOException {
        if (StringUtils.isEmpty(javaFilePath)) {
            compliePath = StringUtils.isEmpty(configClassPath)
                    ? iomClassesPath
                    : configClassPath;
            javaFilePath = compliePath + File.separator + "javaDynamicScript"
                    + File.separator;
            File javaFilePathFile = new File(javaFilePath);
            if (!javaFilePathFile.exists()) {
                javaFilePathFile.mkdirs();
            }
        }

        log.debug("开始编译java文件：" + (javaFilePath));

        File file = new File(javaFilePath + scriptCode + ".java");
        if (!file.exists()) {
            file.createNewFile();
        }

        // file.deleteOnExit();
        String absolutePath = file.getAbsolutePath();
        log.info("<script> file path [" + absolutePath + "]");
        String filename = file.getName();

        PrintWriter out = new PrintWriter(new FileOutputStream(file));
        if (log.isDebugEnabled()) {
            log.debug(
                    "<JavaDynamicScriptExecutor> script is :\n" + scriptBody);
        }
        out.println(scriptBody);
        out.flush();
        out.close();

        try {
            JavaCompiler javaCompiler = ToolProvider.getSystemJavaCompiler();
            StandardJavaFileManager sjf = javaCompiler
                    .getStandardFileManager(null, null, null);
            Iterable<? extends JavaFileObject> it = sjf
                    .getJavaFileObjects(absolutePath);
            Iterable<String> options = Arrays.asList("-classpath",
                    classPathPath);
            JavaCompiler.CompilationTask task = javaCompiler.getTask(null, sjf, null,
                    options, null, it);

            boolean success = task.call();
            if (!success) {
                throw new Exception("script complie error!...");
            }
        } catch (Exception e) {
            // TODO: handle exception
            log.error("使用类加载器动态编译失败!!开始用URLClassLoader加载！！！");
            String[] arg = null;
            if (osName.indexOf("Windows") != -1) {
                arg = new String[]{"-cp", classPathPath, "-d", compliePath,
                        javaFilePath + File.separator + filename};
            } else {
                arg = new String[]{"-cp", classPathPath, "-d", compliePath,
                        javaFilePath + File.separator + filename};
            }
            int status = com.sun.tools.javac.Main.compile(arg);
            if (status > 0) {

            } else {
                throw new RuntimeException("动态编译组件失败!!", e);
            }
        }
    }

    @SuppressWarnings("resource")
    private Class<?> cacheClazz(String className, String shellScriptId) throws Exception {
        log.info("<script> url path=[" + "file:" + compliePath + File.separator + "]");
        DynamicClassLoader urlClass = new DynamicClassLoader(Thread.currentThread().getContextClassLoader());
        Class<?> clazz = urlClass.loadClass(compliePath, "javaDynamicScript." + className);
        putToCache(shellScriptId, clazz);
        return clazz;
    }

    private Class<?> cacheClazz(String className, String shellScriptId, Class<?> clazz) throws Exception {
        putToCache(shellScriptId, clazz);
        return clazz;
    }


    /**
     * 在系统启动初始化动态组件
     *
     * @author
     * @date 2016年12月19日*/

    public void createClassOriginally() {
        List<DynamicCompDto> osBusiDynamicCompDtos = dynamicCompDao.findAll();
        for (DynamicCompDto osBusiDynamicCompDto : osBusiDynamicCompDtos) {
            try {
                createClass(String.valueOf(osBusiDynamicCompDto.getId()), osBusiDynamicCompDto.getCompCode(), osBusiDynamicCompDto.getCompContent());
            } catch (Exception e) {
                log.error("动态组件:osBusiDynamicCompId=" + osBusiDynamicCompDto.getId() + ",编译失败!", e);
            }
        }
    }

  /*  *
     * 从Spring容器中移除动态组件bean
     *
     * @author
     * @date 2017年2月16日*/

    public void removeDynamicBean() {
        //获取所有容器的所有beanNames
        String[] beanNames = defaultList.getBeanDefinitionNames();
        for (String beanName : beanNames) {
            if (beanName.startsWith(dynamicBeanFix)) {//判断如果是动态组件bean则从Spring容器中移除
                try {
                    defaultList.removeBeanDefinition(beanName);
                } catch (NoSuchBeanDefinitionException e) {
                    log.warn("spring容器未找到bean：" + beanName + ",移除失败!!!");
                }
            }
        }
    }

    /**
     * 根据组件ID从Spring容器中移除动态组件bean
     *
     * @param componentId
     * @author
     * @date 2017年3月7日*/

    public boolean removeDynamicBeanByComponentId(String componentId) {
        boolean ret=false;
        try {
            defaultList.removeBeanDefinition(dynamicBeanFix + componentId);
            ret=true;
        } catch (NoSuchBeanDefinitionException e) {
            log.warn("spring容器未找到组件ID为：" + componentId + "的动态组件,移除失败!!!");
        }
        return  ret;
    }


    public static String getClassPathPath() {
        return classPathPath;
    }

    public static void setClassPathPath(String classPathPath) {
        DynmiaClassCacheUtils.classPathPath = classPathPath;
    }


}
